    let posFreezeUntil = 0;
    function now() { try { return performance.now ? performance.now() : Date.now(); } catch { return Date.now(); } }
    function freezePosition(ms = 800){
      try{
        if (!ui.wrapper) return () => { posFreezeUntil = 0; };
        const r = ui.wrapper.getBoundingClientRect();
        const px = (v)=> `${(Math.round(v * 100)/100).toFixed(2)}px`;
        const cs = window.getComputedStyle(ui.wrapper);
        const useRight = cs.right !== 'auto' && cs.right !== '';
        const useBottom = cs.bottom !== 'auto' && cs.bottom !== '';
        if (useRight) {
          // 保持右侧锚点
          const vw = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
          const rightPx = px(Math.max(0, vw - r.right));
          ui.wrapper.style.setProperty('right', rightPx, 'important');
          ui.wrapper.style.setProperty('left', 'auto', 'important');
        } else {
          ui.wrapper.style.setProperty('left', px(r.left), 'important');
          ui.wrapper.style.setProperty('right', 'auto', 'important');
        }
        if (useBottom) {
          const vh = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
          const bottomPx = px(Math.max(0, vh - r.bottom));
          ui.wrapper.style.setProperty('bottom', bottomPx, 'important');
          ui.wrapper.style.setProperty('top', 'auto', 'important');
        } else {
          ui.wrapper.style.setProperty('top', px(r.top), 'important');
          ui.wrapper.style.setProperty('bottom', 'auto', 'important');
        }
        // 仅在必要时移除 transform，避免 Y 轴对齐发生抖动
        if (cs.transform && cs.transform !== 'none') {
          ui.wrapper.style.setProperty('transform', 'none', 'important');
        }
        posFreezeUntil = now() + Math.max(200, ms);
      }catch{}
      const unlock = () => { posFreezeUntil = 0; };
      try { setTimeout(unlock, Math.max(200, ms)); } catch {}
      return unlock;
    }
  // 在写入 localGce 前保留最新 dockIds/dockStamp，避免覆盖 Dock（行为不变，提取复用）
  function saveRootPreservingDock(root) {
    try {
      chrome.storage.local.get(['localGce'], (latest) => {
        try {
          const lset = latest?.localGce?.settings || {};
          const lStamp = Number(lset?.dockStamp || 0);
          const rStamp = Number(root?.settings?.dockStamp || 0);
          if (lStamp > rStamp) {
            root.settings.dockIds = Array.isArray(lset.dockIds) ? lset.dockIds : [];
            root.settings.dockStamp = lStamp;
          }
        } catch {}
        try { chrome.storage.local.set({ localGce: root }, () => {}); } catch {}
      });
    } catch {}
  }
(() => {
  if (typeof window === 'undefined') return;
  if (window.top !== window) return;
  const extensionOrigin = chrome.runtime.getURL('');
  if (window.location.href.startsWith(extensionOrigin)) return;
  if (window.__GCE_FLOATING_MOUNTED__) return;

  const SUPPORTED_PROTOCOLS = ['http:', 'https:'];
  let enableFeature = SUPPORTED_PROTOCOLS.includes(window.location.protocol);
  let mounted = false;
  const ui = { wrapper: null, qrPanel: null };

  function readFabEnabled(cb) {
    try {
      chrome.storage.local.get(['localGce'], (data) => {
        try {
          const on = !!(data && data.localGce && data.localGce.settings ? data.localGce.settings.fabEnabled : true);
          cb(on);
        } catch (e) {
          cb(true);
        }
      });
    } catch (e) {
      cb(true);
    }
  }

  // 读取/保存浮动按钮位置（相对视口的 left/top，单位 px）
  function readFabPosition(cb) {
    try {
      chrome.storage.local.get(['localGce'], (data) => {
        try {
          const pos = data?.localGce?.settings?.fabPos;
          if (pos && typeof pos.left === 'number' && typeof pos.top === 'number') cb({ left: pos.left, top: pos.top });
          else cb(null);
        } catch { cb(null); }
      });
    } catch { cb(null); }
  }
  function saveFabPosition(left, top) {
    try {
      chrome.storage.local.get(['localGce'], (data) => {
        const root = data?.localGce || {};
        const settings = root.settings || {};
        settings.fabPos = { left: Math.max(0, Math.round(left)), top: Math.max(0, Math.round(top)) };
        settings.fabMoved = true;
        try { settings.fabOriginHost = window.location.host || ''; } catch { settings.fabOriginHost = ''; }
        root.settings = settings;
        saveRootPreservingDock(root);
      });
    } catch {}
  }

  // 新增：基于边缘锚点的布局，保证在不同页面/视口下一致（默认右下角）
  function readFabPlacement(cb) {
    try {
      chrome.storage.local.get(['localGce'], (data) => {
        try {
          const plc = data?.localGce?.settings?.fabPlacement;
          if (plc && plc.ax && plc.ay && typeof plc.ox === 'number' && typeof plc.oy === 'number') return cb(plc);
          cb(null);
        } catch { cb(null); }
      });
    } catch { cb(null); }
  }
  function saveFabPlacement(ax, ay, ox, oy) {
    try {
      chrome.storage.local.get(['localGce'], (data) => {
        const root = data?.localGce || {};
        const settings = root.settings || {};
        settings.fabPlacement = { ax, ay, ox: Math.max(0, Math.round(ox)), oy: Math.max(0, Math.round(oy)) };
        root.settings = settings;
        saveRootPreservingDock(root);
      });
    } catch {}
  }

  function applyPlacement(wrapper, plc) {
    try {
      const rect = wrapper.getBoundingClientRect();
      const vw = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
      const vh = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
      const margin = 12; // 与 CSS 默认 right/bottom:12px 保持一致
      const maxLeft = Math.max(0, vw - rect.width - margin);
      const maxTop = Math.max(0, vh - rect.height - margin);
      
      // 移除 transform，使用绝对定位
      wrapper.style.transform = 'none';
      
      if (plc?.ax === 'left') {
        const l = Math.min(maxLeft, Math.max(margin, plc.ox));
        wrapper.style.left = `${l}px`;
        wrapper.style.right = 'auto';
      } else { // right
        const maxRight = Math.max(margin, vw - rect.width - margin);
        const r = Math.min(maxRight, Math.max(margin, plc.ox));
        wrapper.style.right = `${r}px`;
        wrapper.style.left = 'auto';
      }
      if (plc?.ay === 'top') {
        const t = Math.min(maxTop, Math.max(margin, plc.oy));
        wrapper.style.top = `${t}px`;
        wrapper.style.bottom = 'auto';
      } else { // bottom
        const maxBottom = Math.max(margin, vh - rect.height - margin);
        const b = Math.min(maxBottom, Math.max(margin, plc.oy));
        wrapper.style.bottom = `${b}px`;
        wrapper.style.top = 'auto';
      }
    } catch {}
  }

  function convertLegacyPosToPlacement(pos, wrapper) {
    // 将 {left, top} 转换为距离最近边缘的锚点+偏移
    try {
      const rect = wrapper.getBoundingClientRect();
      const vw = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
      const vh = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
      const distLeft = Math.max(0, pos.left);
      const distRight = Math.max(0, vw - rect.width - pos.left);
      const distTop = Math.max(0, pos.top);
      const distBottom = Math.max(0, vh - rect.height - pos.top);
      const ax = distLeft <= distRight ? 'left' : 'right';
      const ay = distTop <= distBottom ? 'top' : 'bottom';
      const ox = ax === 'left' ? distLeft : distRight;
      const oy = ay === 'top' ? distTop : distBottom;
      return { ax, ay, ox, oy };
    } catch { return null; }
  }

  function setFabEnabled(value) {
    try {
      chrome.storage.local.get(['localGce'], (data) => {
        const root = data?.localGce || {};
        const settings = root.settings || {};
        settings.fabEnabled = !!value;
        root.settings = settings;
        saveRootPreservingDock(root);
      });
    } catch {}
  }

  function waitForBody(cb) {
    if (document.body) {
      cb();
      return;
    }
    const observer = new MutationObserver(() => {
      if (document.body) {
        observer.disconnect();
        cb();
      }
    });
    observer.observe(document.documentElement || document, { childList: true, subtree: true });
  }

  function waitForComplete(cb) {
    try {
      if (document.readyState === 'complete') {
        cb();
        return;
      }
      const onLoad = () => {
        try { window.removeEventListener('load', onLoad, true); } catch {}
        cb();
      };
      window.addEventListener('load', onLoad, true);
    } catch {
      waitForBody(cb);
    }
  }

  function loadStyle() {
    const id = 'gce-floating-style';
    if (document.getElementById(id)) return;
    const link = document.createElement('link');
    link.id = id;
    link.rel = 'stylesheet';
    link.href = chrome.runtime.getURL('content/floatingButtons.css');
    (document.head || document.documentElement).appendChild(link);
  }

  function ensureIconSymbols() {
    const holderId = 'gce-iconfont-holder';
    if (document.getElementById(holderId)) return;

    function injectSvg() {
      if (document.getElementById(holderId)) return;
      if (!window._iconfont_svg_string_5021397) return;
      const holder = document.createElement('div');
      holder.id = holderId;
      holder.style.display = 'none';
      holder.innerHTML = window._iconfont_svg_string_5021397;
      (document.body || document.documentElement).appendChild(holder);
    }

    if (window._iconfont_svg_string_5021397) {
      injectSvg();
      return;
    }

    const scriptId = 'gce-iconfont-loader';
    if (document.getElementById(scriptId)) return;
    const script = document.createElement('script');
    script.id = scriptId;
    script.type = 'text/javascript';
    script.src = chrome.runtime.getURL('src/newtab/assets/iconfont/iconfont.js');
    script.onload = () => {
      try { script.remove(); } catch {}
      injectSvg();
    };
    (document.documentElement || document.body).appendChild(script);
  }

  function iconSvg(symbol){
    return `<svg class="gce-icon" viewBox="0 0 1024 1024" width="20" height="20" aria-hidden="true"><use href="#${symbol}" xlink:href="#${symbol}"></use></svg>`;
  }

  function inlineStar(filled){
    return filled
      ? '<svg class="gce-icon" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M12 17.27 18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>'
      : '<svg class="gce-icon" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M22 9.24l-7.19-.62L12 2 9.19 8.62 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zm-10 6.11l-3.76 2.27 1-4.28L6 10.5l4.38-.38L12 6l1.62 4.12 4.38.38-3.22 2.84 1 4.28L12 15.35z"/></svg>';
  }

  function pickStarSvg(filled){
    const candidatesFilled = ['icon-shoucang-shixin','icon-shoucang-fill','icon-star-fill','icon-star1','icon-aixin_shixin'];
    const candidatesOutline = ['icon-shoucang','icon-shoucang-kongxin','icon-star','icon-star-line','icon-aixin'];
    try {
      const holder = document.getElementById('gce-iconfont-holder');
      if (holder) {
        const list = (filled ? candidatesFilled : candidatesOutline);
        for (const id of list) { if (holder.querySelector(`symbol#${id}`)) return iconSvg(id); }
      }
    } catch {}
    return inlineStar(filled);
  }

  function normalizeUrl(u){
    try { const x = new URL(u); return x.href; } catch { return u || ''; }
  }

  function isBookmarked(cb){
    try {
      chrome.storage.local.get(['localGce'], (data) => {
        try {
          const list = Array.isArray(data?.localGce?.bookmarks) ? data.localGce.bookmarks : [];
          const cur = normalizeUrl(window.location.href);
          const ok = list.some(b => normalizeUrl(b?.url) === cur);
          cb(!!ok);
        } catch { cb(false); }
      });
    } catch { cb(false); }
  }

  function createToast() {
    const toast = document.createElement('div');
    toast.className = 'gce-floating-toast';
    document.body.appendChild(toast);
    let timer = null;
    return {
      show(message, type = 'success', ms = 2600) {
        toast.textContent = message;
        toast.classList.remove('success', 'error');
        toast.classList.add(type);
        toast.classList.add('show');
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => {
          toast.classList.remove('show');
        }, ms);
      }
    };
  }

  function createButton(icon, label) {
    const btn = document.createElement('button');
    btn.type = 'button';
    btn.className = 'gce-floating-button';
    btn.innerHTML = `${icon}<span class="gce-floating-button__label">${label}</span>`;
    return btn;
  }

  function ensureQrLib() {
    if (window.QRCode) {
      window.__GCE_QR_READY__ = true;
      return Promise.resolve();
    }
    const err = new Error('二维码脚本尚未初始化');
    try { import(chrome.runtime.getURL('shared/log.js')).then(({ LOGE }) => LOGE('FAB', 'QRCode 未定义，请检查扩展注入顺序', err)).catch(()=>{}); } catch {}
    return Promise.reject(err);
  }

  function initUi() {
    loadStyle();
    ensureIconSymbols();

    const wrapper = document.createElement('div');
    wrapper.className = 'gce-floating-entry';
    const stack = document.createElement('div');
    stack.className = 'gce-floating-stack';
    wrapper.appendChild(stack);

    const toast = createToast();

    const qrPanel = document.createElement('div');
    qrPanel.className = 'gce-floating-qr';
    const qrClose = document.createElement('div');
    qrClose.className = 'gce-floating-qr__close';
    qrClose.textContent = '×';
    const qrTitle = document.createElement('div');
    qrTitle.className = 'gce-floating-qr__title';
    qrTitle.textContent = '扫码打开当前页面';
    const qrContainer = document.createElement('div');
    qrPanel.append(qrClose, qrTitle, qrContainer);

    qrClose.addEventListener('click', () => {
      qrPanel.classList.remove('active');
      qrContainer.innerHTML = '';
    });

    const btnQr = createButton(iconSvg('icon-erweima'), '生成二维码');
    btnQr.addEventListener('click', async () => {
      if (!enableFeature) {
        toast.show('当前页面不支持生成二维码', 'error');
        return;
      }
      if (qrPanel.classList.contains('active')) {
        qrPanel.classList.remove('active');
        qrContainer.innerHTML = '';
        return;
      }
      try {
        await ensureQrLib();
        qrContainer.innerHTML = '';
        const QR = window.QRCode || (typeof QRCode !== 'undefined' ? QRCode : null);
        if (!QR) {
          throw new Error('二维码库未正确挂载');
        }
        try { const { LOGD } = await import(chrome.runtime.getURL('shared/log.js')); LOGD('FAB', '开始生成二维码'); } catch {}
        new QR(qrContainer, {
          text: window.location.href,
          width: 180,
          height: 180,
          colorDark: '#111111',
          colorLight: '#ffffff',
          correctLevel: (QR.CorrectLevel && QR.CorrectLevel.H) || 0,
        });
        qrPanel.classList.add('active');
      } catch (err) {
        try { const { LOGE } = await import(chrome.runtime.getURL('shared/log.js')); LOGE('FAB', '生成二维码失败', err); } catch {}
        qrPanel.classList.remove('active');
        qrContainer.innerHTML = '';
        toast.show('二维码生成失败', 'error');
      }
    });
    stack.appendChild(btnQr);

    const btnTop = createButton(iconSvg('icon-fanhuidingbu'), '返回顶部');
    btnTop.addEventListener('click', () => {
      window.scrollTo({ top: 0, behavior: 'smooth' });
    });
    stack.appendChild(btnTop);

    let bookmarkedFlag = false;
    const btnAdd = createButton(pickStarSvg(false), '收藏到 GCE');
    btnAdd.addEventListener('click', async () => {
      if (!enableFeature) {
        toast.show('仅支持收藏 http(s) 页面', 'error');
        return;
      }
      // 若已收藏，则执行取消收藏
      if (bookmarkedFlag) {
        const unlockRm = freezePosition(1000);
        chrome.runtime.sendMessage({ type: 'GCE_REMOVE_BOOKMARK', payload: { url: window.location.href } }, (resp) => {
          try { unlockRm && unlockRm(); } catch {}
          const lastError = chrome.runtime.lastError;
          if (lastError) {
            toast.show(lastError.message || '取消收藏失败', 'error');
            return;
          }
          if (!resp || !resp.ok) {
            toast.show(resp?.message || '取消收藏失败', 'error');
            return;
          }
          if (resp.removed) {
            bookmarkedFlag = false;
            try { updateStar(false); } catch {}
            toast.show('已取消收藏', 'success');
          }
        });
        return;
      }
      let groupId = 'default';
      try {
        groupId = await resolveDefaultGroupId();
      } catch (err) {
        try { const { LOGW } = await import(chrome.runtime.getURL('shared/log.js')); LOGW('FAB', '解析默认分组失败，使用 default', err); } catch {}
      }
      chrome.runtime.sendMessage({
        type: 'GCE_ADD_BOOKMARK',
        payload: {
          url: window.location.href,
          title: document.title || window.location.href,
          groupId,
        }
      }, (resp) => {
        const lastError = chrome.runtime.lastError;
        if (lastError) {
          toast.show(lastError.message || '收藏失败', 'error');
          try { import(chrome.runtime.getURL('shared/log.js')).then(({ LOGE }) => LOGE('FAB', '收藏失败(runtime)', lastError)).catch(()=>{}); } catch {}
          return;
        }
        try { import(chrome.runtime.getURL('shared/log.js')).then(({ LOGD }) => LOGD('FAB', '收藏响应', resp)).catch(()=>{}); } catch {}
        if (!resp || !resp.ok) {
          toast.show(resp?.message || '收藏失败', 'error');
          try { import(chrome.runtime.getURL('shared/log.js')).then(({ LOGE }) => LOGE('FAB', '收藏失败(response)', resp)).catch(()=>{}); } catch {}
          return;
        }
        const unlock = freezePosition(1000);
        if (resp.duplicated) {
          try { updateStar(true); } catch {}
          bookmarkedFlag = true;
          try { unlock && unlock(); } catch {}
          return;
        }
        try { updateStar(true); } catch {}
        bookmarkedFlag = true;
        try { unlock && unlock(); } catch {}
        toast.show('已添加到 GCE', 'success');
      });
    });
    stack.appendChild(btnAdd);

    // 新增：移动按钮（按住拖拽以移动位置）
    const btnMove = createButton(iconSvg('icon-yidong'), '移动');
    btnMove.classList.add('move');
    stack.appendChild(btnMove);

    (function attachDrag() {
      let dragging = false;
      let moved = false;
      let startX = 0, startY = 0; // 指针起点
      let grabOffsetX = 0, grabOffsetY = 0; // 指针相对 wrapper 左上角的偏移

      function setWrapperPosition(left, top) {
        const rect = wrapper.getBoundingClientRect();
        const vw = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
        const vh = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
        const margin = 12; // 与 CSS/锚点布局保持一致
        const maxLeft = Math.max(0, vw - rect.width - margin);
        const maxTop = Math.max(0, vh - rect.height - margin);
        const l = Math.min(maxLeft, Math.max(margin, left));
        const t = Math.min(maxTop, Math.max(margin, top));
        wrapper.style.left = `${l}px`;
        wrapper.style.top = `${t}px`;
        wrapper.style.right = 'auto';
        wrapper.style.bottom = 'auto';
        wrapper.style.transform = 'none'; // 移除 translateY，避免拖动时位置计算错误
        return { l, t };
      }

      function onPointerMove(e) {
        if (!dragging) return;
        const dx = e.clientX - startX;
        const dy = e.clientY - startY;
        // 小阈值，避免点击即触发移动
        if (!moved && Math.abs(dx) + Math.abs(dy) < 3) return;
        if (!moved) moved = true;
        const left = e.clientX - grabOffsetX;
        const top = e.clientY - grabOffsetY;
        setWrapperPosition(left, top);
      }
      function onPointerUp() {
        if (!dragging) return;
        dragging = false;
        document.removeEventListener('pointermove', onPointerMove);
        document.removeEventListener('pointerup', onPointerUp, true);
        if (moved) {
          moved = false;
          try {
            const rect = wrapper.getBoundingClientRect();
            const left = Math.max(0, Math.round(rect.left));
            const top = Math.max(0, Math.round(rect.top));
            saveFabPosition(left, top);
          } catch {}
        }
      }
      btnMove.addEventListener('pointerdown', (e) => {
        try { e.preventDefault(); e.stopPropagation(); } catch {}
        const rect = wrapper.getBoundingClientRect();
        dragging = true;
        moved = false;
        startX = e.clientX;
        startY = e.clientY;
        // 记录抓取点相对 wrapper 的偏移，确保拖拽过程中不发生基点跳变
        grabOffsetX = startX - rect.left;
        grabOffsetY = startY - rect.top;
        try { btnMove.setPointerCapture && btnMove.setPointerCapture(e.pointerId); } catch {}
        document.addEventListener('pointermove', onPointerMove);
        document.addEventListener('pointerup', onPointerUp, true);
      });
    })();

    // 新增：关闭按钮（等效于设置中的“启用浮动按钮组”关闭）
    const btnClose = createButton(iconSvg('icon-guanbi'), '关闭');
    btnClose.addEventListener('click', () => {
      setFabEnabled(false);
      toast.show('已关闭浮动按钮组');
    });
    stack.appendChild(btnClose);

    // 在插入 DOM 之前，设置为右侧中部并隐藏，避免任何闪烁
    try {
      wrapper.style.setProperty('position', 'fixed', 'important');
      wrapper.style.setProperty('z-index', '2147483600', 'important');
      wrapper.style.setProperty('right', '12px', 'important');
      wrapper.style.setProperty('top', '50%', 'important');
      wrapper.style.setProperty('left', 'auto', 'important');
      wrapper.style.setProperty('bottom', 'auto', 'important');
      wrapper.style.setProperty('transform', 'translateY(-50%)', 'important');
      wrapper.style.opacity = '0';
    } catch {}

    document.body.appendChild(wrapper);
    document.body.appendChild(qrPanel);

    // 记录引用，便于销毁
    ui.wrapper = wrapper;
    ui.qrPanel = qrPanel;
    mounted = true;
    window.__GCE_FLOATING_MOUNTED__ = true;

    function updateStar(filled){
      const unlock = freezePosition(600);
      try { const icon = btnAdd.querySelector('svg.gce-icon'); if (icon) icon.outerHTML = pickStarSvg(!!filled); } catch {}
      try { unlock && unlock(); } catch {}
    }
    try { isBookmarked((ok)=> { bookmarkedFlag = !!ok; updateStar(!!ok); }); } catch {}

    // 默认：右侧中部（已在插入前设置，这里仅保留注释与可能的站点覆盖重申在后续流程中处理）

    // 应用已保存的位置（优先使用绝对坐标，其次兼容旧版锚点一次性迁移）
    function applyPos(wrapper, left, top){
      try{
        const rect = wrapper.getBoundingClientRect();
        const vw = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
        const vh = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
        const margin = 12;
        const maxLeft = Math.max(0, vw - rect.width - margin);
        const maxTop = Math.max(0, vh - rect.height - margin);
        const l = Math.min(maxLeft, Math.max(margin, left));
        const t = Math.min(maxTop, Math.max(margin, top));
        wrapper.style.setProperty('left', `${l}px`, 'important');
        wrapper.style.setProperty('top', `${t}px`, 'important');
        wrapper.style.setProperty('right', 'auto', 'important');
        wrapper.style.setProperty('bottom', 'auto', 'important');
        wrapper.style.setProperty('transform', 'none', 'important');
      }catch{}
    }

    // 新策略：仅当用户曾拖拽过(fabMoved=true)时应用 fabPos；否则保持默认右侧中部
    function setDefaultRightCenter(){
      try{
        wrapper.style.setProperty('position', 'fixed', 'important');
        wrapper.style.setProperty('z-index', '2147483600', 'important');
        wrapper.style.setProperty('right', '12px', 'important');
        wrapper.style.setProperty('top', '50%', 'important');
        wrapper.style.setProperty('left', 'auto', 'important');
        wrapper.style.setProperty('bottom', 'auto', 'important');
        wrapper.style.setProperty('transform', 'translateY(-50%)', 'important');
      }catch{}
    }
    function reveal(){ try { wrapper.classList.add('ready'); wrapper.style.opacity = '1'; } catch {} }
    function enforceDefaultCascade(){
      setDefaultRightCenter();
      try {
        requestAnimationFrame(()=>{
          setDefaultRightCenter();
          requestAnimationFrame(()=>{ setDefaultRightCenter(); });
        });
      } catch {}
      try { setTimeout(setDefaultRightCenter, 0); setTimeout(setDefaultRightCenter, 250); } catch {}
    }
    try {
      chrome.storage.local.get(['localGce'], (data) => {
        try {
          const settings = data?.localGce?.settings || {};
          const moved = !!settings.fabMoved;
          const pos = settings.fabPos;
          const originHost = settings.fabOriginHost || '';
          const sameHost = (function(){ try { return (originHost && originHost === (window.location.host || '')); } catch { return false; } })();
          if (moved && sameHost && pos && typeof pos.left === 'number' && typeof pos.top === 'number') {
            // 若检测到历史位置贴近左侧，进行一次性纠偏到右侧并持久化
            try {
              const rect = wrapper.getBoundingClientRect();
              const vw = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
              const margin = 12;
              const nearLeft = pos.left <= (margin * 2);
              if (nearLeft) {
                const newLeft = Math.max(margin, vw - rect.width - margin);
                applyPos(wrapper, newLeft, pos.top);
                saveFabPosition(newLeft, pos.top);
                reveal();
                return;
              }
            } catch {}
            applyPos(wrapper, pos.left, pos.top);
            reveal();
            return;
          }
          // 兼容旧锚点：若存在且未标记 moved，则迁移一次为绝对坐标但不改变默认显示
          const plc = settings.fabPlacement;
          if (!moved && plc && plc.ax && plc.ay && typeof plc.ox === 'number' && typeof plc.oy === 'number') {
            try {
              applyPlacement(wrapper, plc);
              const rect = wrapper.getBoundingClientRect();
              const left = Math.max(0, Math.round(rect.left));
              const top = Math.max(0, Math.round(rect.top));
              // 仅保存为绝对坐标，不立即覆盖默认右侧位置，等待用户首次拖拽再启用
              try {
                chrome.storage.local.get(['localGce'], (d2) => {
                  const root2 = d2?.localGce || {};
                  const set2 = root2.settings || {};
                  set2.fabPos = { left, top };
                  // 不设置 fabMoved，这样不会自动应用
                  root2.settings = set2;
                  saveRootPreservingDock(root2);
                });
              } catch {}
              // 还原为默认右侧中部
              enforceDefaultCascade();
              reveal();
            } catch {}
            return;
          }
          // 无任何位置数据，直接显示默认
          enforceDefaultCascade();
          reveal();
        } catch {}
      });
    } catch {}

    // 在未移动状态下，窗口尺寸变化时保持默认右侧中部
    try {
      window.addEventListener('resize', () => {
        try {
          chrome.storage.local.get(['localGce'], (data) => {
            const moved = !!(data?.localGce?.settings?.fabMoved);
            if (!moved) setDefaultRightCenter();
          });
        } catch {}
      });
    } catch {}
  }

  function destroyUi(){
    try{ if (ui.qrPanel && ui.qrPanel.parentNode) ui.qrPanel.parentNode.removeChild(ui.qrPanel); }catch{}
    try{ if (ui.wrapper && ui.wrapper.parentNode) ui.wrapper.parentNode.removeChild(ui.wrapper); }catch{}
    ui.wrapper = null; ui.qrPanel = null; mounted = false; window.__GCE_FLOATING_MOUNTED__ = false;
  }

  async function resolveDefaultGroupId() {
    try {
      const meta = document.querySelector('meta[name="gce-default-group"]');
      if (meta) {
        const value = meta.getAttribute('content');
        if (value) return value;
      }
    } catch (err) {
      console.debug('[GCE][FAB] 从 meta 读取默认分组失败', err);
    }

    if (chrome?.storage?.local?.get) {
      try {
        const res = await new Promise((resolve) => {
          try {
            chrome.storage.local.get(['localGce'], (data) => {
              resolve(data?.localGce);
            });
          } catch (err) {
            resolve(undefined);
          }
        });
        const groups = Array.isArray(res?.groups) ? res.groups.slice() : [];
        const sorted = groups.sort((a,b)=> (a?.order||0)-(b?.order||0));
        const firstId = sorted[0]?.id;
        if (firstId) return firstId;
      } catch (err) {
        console.debug('[GCE][FAB] 从 storage 读取分组失败', err);
      }
    }

    return 'default';
  }

  readFabEnabled((ok) => {
    if (ok) {
      if (!mounted) waitForComplete(initUi);
    } else {
      if (mounted) destroyUi();
    }
  });

  // 监听 storage 变化，实时启用/停用，无需刷新
  try{
    const st = chrome?.storage;
    if (st?.onChanged?.addListener) {
      st.onChanged.addListener((changes, area) => {
        if (area !== 'local' || !changes || !('localGce' in changes)) return;
        const nv = changes.localGce?.newValue;
        const enabled = !!(nv && nv.settings ? nv.settings.fabEnabled : true);
        if (!enabled) {
          if (mounted) destroyUi();
        } else {
          if (!mounted) waitForComplete(initUi);
        }
        // 若位置发生变化，应用到已挂载实例
        try {
          if (!mounted || !ui.wrapper) return;
          const pos = nv?.settings?.fabPos;
          if (pos && typeof pos.left === 'number' && typeof pos.top === 'number') {
            if (now() < posFreezeUntil) return; // 冻结窗口内不调整位置
            // 直接按绝对坐标应用
            const rect = ui.wrapper.getBoundingClientRect();
            const left = typeof pos.left === 'number' ? pos.left : Math.max(0, Math.round(rect.left));
            const top = typeof pos.top === 'number' ? pos.top : Math.max(0, Math.round(rect.top));
            // 复用上文 applyPos（在作用域内重写一份以避免引用问题）
            (function applyPosLocal(wrapper, lft, tp){
              try{
                const r = wrapper.getBoundingClientRect();
                const vw = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
                const vh = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
                const margin = 12;
                const maxLeft = Math.max(0, vw - r.width - margin);
                const maxTop = Math.max(0, vh - r.height - margin);
                const l = Math.min(maxLeft, Math.max(margin, lft));
                const t = Math.min(maxTop, Math.max(margin, tp));
                wrapper.style.left = `${l}px`;
                wrapper.style.top = `${t}px`;
                wrapper.style.right = 'auto';
                wrapper.style.bottom = 'auto';
                wrapper.style.transform = 'none';
              }catch{}
            })(ui.wrapper, left, top);
            return;
          }
          // 兼容旧数据：若仅有锚点，迁移一次到绝对坐标
          const plc = nv?.settings?.fabPlacement;
          if (plc && plc.ax && plc.ay) {
            if (now() < posFreezeUntil) return; // 冻结窗口内不调整位置
            applyPlacement(ui.wrapper, plc);
            const r2 = ui.wrapper.getBoundingClientRect();
            const l2 = Math.max(0, Math.round(r2.left));
            const t2 = Math.max(0, Math.round(r2.top));
            ui.wrapper.style.left = `${l2}px`;
            ui.wrapper.style.top = `${t2}px`;
            ui.wrapper.style.right = 'auto';
            ui.wrapper.style.bottom = 'auto';
            ui.wrapper.style.transform = 'none';
            saveFabPosition(l2, t2);
          }
        } catch {}

        // 书签变化时，更新星标状态
        try {
          if (!mounted) return;
          const list = Array.isArray(nv?.bookmarks) ? nv.bookmarks : [];
          const cur = (function(u){ try { return new URL(u).href; } catch { return u || ''; } })(window.location.href);
          const hit = list.some(b => (function(u){ try { return new URL(u).href; } catch { return u || ''; } })(b?.url) === cur);
          bookmarkedFlag = !!hit;
          try { updateStar(!!hit); } catch {}
        } catch {}
      });
    }
  }catch{}
})();
